package com.epoch.base.export;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;

import com.epoch.base.util.LoggerUtil;
import com.epoch.platform.common.util.I18nUtils;

public abstract class ExcelExporter extends AbstractExporter {
    private static final Logger LOGGER = LoggerUtil.getLogger();
    private static final String CHARSET_FOR_COL_WIDTH = "GBK";

    @Override
    public void doExport(OutputStream out, Collection<?> data, List<HeadMeta> headMetas,
        Map<Integer, IExportConvertor> indexCvtorMap, Map<String, IExportConvertor> fieldCvtorMap,
        Map<String, Object> params) throws IOException, ExportException {
        int maxRowCnt = this.maxRowCnt();
        if (data != null && data.size() + 1 > maxRowCnt) {
            String ext = this.getFileNameExt();
            throw new ExportException("Export failed: '" + this.getFileNameExt()
                + "' format can not support more than " + String.valueOf(maxRowCnt) + " rows.", I18nUtils.get("", ext,
                Integer.valueOf(maxRowCnt)));
        }
        int maxColCnt = this.maxColCnt();
        if (headMetas != null && headMetas.size() > maxColCnt) {
            throw new ExportException("Export failed: '" + this.getFileNameExt()
                + "' format can not support more than " + maxColCnt + " columns.");
        }
        Workbook book = this.buildWorkbook();
        this.exportExcel(data, book, headMetas, indexCvtorMap, fieldCvtorMap, params);
        book.write(out);
        out.flush();
    }

    protected abstract int maxRowCnt();

    protected abstract int maxColCnt();

    protected abstract Workbook buildWorkbook();

    private void exportExcel(Collection<?> data, Workbook book, List<HeadMeta> headMetas,
        Map<Integer, IExportConvertor> indexCvtorMap, Map<String, IExportConvertor> fieldCvtorMap,
        Map<String, Object> params) {
        String sheetName = (String)params.get(ExportConstant.EXPORT_SHEET_NAME);
        Sheet sheet = book.createSheet(sheetName);
        if (headMetas != null) {
            CellStyle style = book.createCellStyle();
            Font font = book.createFont();
            //font.setBold(true);
            style.setFont(font);
            List<Integer> colMaxStrLenList = new ArrayList<Integer>(headMetas.size());
            int excelRowIdx = 0;
            Row headRow = sheet.createRow(excelRowIdx);
            int c = 0;
            int cols = headMetas.size();
            for (; c < cols; c++) {
                HeadMeta meta = headMetas.get(c);
                String title = meta.getTitle();
                Cell cell = headRow.createCell(c, Cell.CELL_TYPE_STRING);
                cell.setCellValue(title);
                cell.setCellStyle(style);
                Integer colLen = null;
                try {
                    colLen = title == null ? null : Integer.valueOf(title.getBytes(CHARSET_FOR_COL_WIDTH).length);
                } catch (UnsupportedEncodingException e) {
                    LOGGER.error("unsupported charset for calculating cell width.", e);
                }
                colMaxStrLenList.add(colLen);
            }
            excelRowIdx++;
            if (data != null) {
                CellStyle datetimeCellStyle = book.createCellStyle();
                datetimeCellStyle.setDataFormat(book.createDataFormat().getFormat(ExportConstant.DATETIME_PATTERN));
                int r = 0;
                for (Object rowData : data) {
                    Row row = sheet.createRow(excelRowIdx);
                    c = 0;
                    for (; c < cols; c++) {
                        HeadMeta meta = headMetas.get(c);
                        String field = meta.getField();
                        Object val = this.obtainFieldVal(rowData, indexCvtorMap, fieldCvtorMap, field, r, c);
                        this.createCell(row, c, val, datetimeCellStyle);
                        Integer maxColLen = colMaxStrLenList.get(c);
                        Integer colLen = this.calcValLen(val);
                        if (colLen != null && (maxColLen == null || colLen.compareTo(maxColLen) > 0)) {
                            colMaxStrLenList.set(c, colLen);
                        }
                    }
                    r++;
                    excelRowIdx++;
                }
            }
            for (c = 0; c < cols; c++) {
                Integer colLen = colMaxStrLenList.get(c);
                if (colLen == null) {
                    sheet.autoSizeColumn(c);
                } else {
                    sheet.setColumnWidth(c, colLen * 256 + 256);
                }
            }
        }
    }

    private Integer calcValLen(Object val) {
        Integer len = null;
        if (val != null) {
            if (val instanceof Boolean) {
                len = Integer.valueOf(5);
            } else if (val instanceof Date) {
                len = Integer.valueOf(ExportConstant.DATETIME_PATTERN.getBytes().length);
            } else {
                try {
                    len = Integer.valueOf(val.toString().getBytes(CHARSET_FOR_COL_WIDTH).length);
                } catch (UnsupportedEncodingException e) {
                    LOGGER.error("unsupported charset for calculating cell width.", e);
                }
            }
        }
        return len;
    }

    private void createCell(Row row, int c, Object val, CellStyle datetimeCellStyle) {
        Cell cell = row.createCell(c);
        if (val != null) {
            if (val instanceof Number) {
                cell.setCellValue(((Number)val).doubleValue());
            } else if (val instanceof Date) {
                cell.setCellValue((Date)val);
                cell.setCellStyle(datetimeCellStyle);
            } else if (val instanceof Boolean) {
                cell.setCellValue(((Boolean)val).booleanValue());
            } else {
                cell.setCellType(Cell.CELL_TYPE_STRING);
                cell.setCellValue(val.toString());
            }
        }
    }
}
